using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Storage.EntityModels;
using Storage;

namespace Search.LOASQL
{
    /// <summary>
    /// This parser has been modified so don't remake a new one with coco without backup.
    /// </summary>
    public class Parser
    {
        const int _EOF = 0;
        const int _ident = 1;
        const int _number = 2;
        const int _string = 3;
        const int maxT = 18;

        const bool T = true;
        const bool x = false;
        const int minErrDist = 2;

        public Scanner scanner;
        public Errors errors;

        public Token t;    // last recognized token
        public Token la;   // lookahead token
        int errDist = minErrDist;

        ParameterExpression pe;
        public ParameterExpression ExprParam { get { return pe; } }

        Type theType;
        public Type ExprType { get{return theType;} }

        Expression exp;
        public Expression Exp { get { return exp; } }

        //TODO Find a way to return either User and Trip.
        public Expression<Func<bool>> Predicate { 
            get 
            {
                return Expression.Lambda<Func<bool>>(exp, pe); 
            } 
        }

        public Parser(Scanner scanner)
        {
            this.scanner = scanner;
            errors = new Errors();
        }

        void SynErr(int n)
        {
            if (errDist >= minErrDist) errors.SynErr(la.line, la.col, n);
            errDist = 0;
        }

        public void SemErr(string msg)
        {
            if (errDist >= minErrDist) errors.SemErr(t.line, t.col, msg);
            errDist = 0;
        }

        void Get()
        {
            for (; ; )
            {
                t = la;
                la = scanner.Scan();
                if (la.kind <= maxT) { ++errDist; break; }

                la = t;
            }
        }

        void Expect(int n)
        {
            if (la.kind == n) Get(); else { SynErr(n); }
        }

        bool StartOf(int s)
        {
            return set[s, la.kind];
        }

        void ExpectWeak(int n, int follow)
        {
            if (la.kind == n) Get();
            else
            {
                SynErr(n);
                while (!StartOf(follow)) Get();
            }
        }


        bool WeakSeparator(int n, int syFol, int repFol)
        {
            int kind = la.kind;
            if (kind == n) { Get(); return true; }
            else if (StartOf(repFol)) { return false; }
            else
            {
                SynErr(n);
                while (!(set[syFol, kind] || set[repFol, kind] || set[0, kind]))
                {
                    Get();
                    kind = la.kind;
                }
                return StartOf(syFol);
            }
        }


        void LOASQL()
        {
            Expect(4);
            Table();
            Expect(5);
            exp = ExprList();
        }

        void Table()
        {
            if (la.kind == 8)
            {   //Users
                Get();
                pe = Expression.Parameter(typeof(User), "p");
                theType = typeof(User);
                
            }
            else if (la.kind == 9)
            {   //Trips
                Get();
                pe = Expression.Parameter(typeof(Trip), "p");
                theType = typeof(Trip);
            }
            else SynErr(19);
        }

        Expression ExprList()
        {
            Expression temp;
            temp = Expr();
            while (la.kind == 16 || la.kind == 17)
            {
                temp = BOp(temp);
            }
            return temp;
        }

        Expression Expr()
        {
            if (la.kind == 1)
            {
                return Term();
            }
            else if (la.kind == 6)
            {
                Get();
                Expression temp = ExprList();
                Expect(7);
                return temp;
            }
            else
            {
                SynErr(20);
                return null;
            }
        }

        Expression BOp(Expression left)
        {
            if (la.kind == 16)
            {        // or
                Get();
                return Expression.OrElse(left, Expr());
                //Expr();
            }
            else if (la.kind == 17)
            { // and
                Get();
                return Expression.AndAlso(left, Expr());
                //Expr();
            }
            else
            {
                SynErr(21);
                return null;
            }
        }

        Expression Term()
        {
            Expression temp;
            temp = Field();
            return Op(temp);
            //Value();
        }

        Expression Field()
        {
            Expect(1);
            
            System.Reflection.PropertyInfo prop = theType.GetProperty(t.val);
            if (prop != null)
            {
                return Expression.Property(pe, prop);
            }
            else
            {
                throw new FatalError("The specified property does not exist.");
            }
        }

        Expression Op(Expression left)
        {
            switch (la.kind)
            {
                case 10:
                    {  // like
                        Get();
                        return Expression.Call(left, typeof(string).GetMethod("Contains"), Value());
                    }
                case 11:
                    {  // >
                        Get();
                        return Expression.GreaterThan(left, Value());
                    }
                case 12:
                    {  // <
                        Get();
                        return Expression.LessThan(left, Value());
                    }
                case 13:
                    {  // =
                        Get();
                        return Expression.Equal(left, Value());
                    }
                case 14:
                    { // >=
                        Get();
                        return Expression.GreaterThanOrEqual(left, Value());
                    }
                case 15:
                    {  // <=
                        Get();
                        return Expression.LessThanOrEqual(left, Value());
                    }
                default:
                    {
                        SynErr(22);
                        return null;
                    }
            }
        }

        Expression Value()
        {
            if (la.kind == 2)
            {         // number
                Get();
                return Expression.Constant(t.val);
            }
            else if (la.kind == 3)
            {  // string
                Get();
                return Expression.Constant(t.val.Substring(1,t.val.Length-2), typeof(string));
            }
            else
            {
                SynErr(23);
                return null;
            }
        }



        public void Parse()
        {
            la = new Token();
            la.val = "";
            Get();
            LOASQL();

            Expect(0);
        }

        static readonly bool[,] set = {
		{T,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x}

	};
    } // end Parser


    public class Errors
    {
        public int count = 0;                                    // number of errors detected
        public System.IO.TextWriter errorStream = Console.Out;   // error messages go to this stream
        public string errMsgFormat = "-- line {0} col {1}: {2}"; // 0=line, 1=column, 2=text

        public void SynErr(int line, int col, int n)
        {
            string s;
            switch (n)
            {
                case 0: s = "EOF expected"; break;
                case 1: s = "ident expected"; break;
                case 2: s = "number expected"; break;
                case 3: s = "string expected"; break;
                case 4: s = "\"from\" expected"; break;
                case 5: s = "\"where\" expected"; break;
                case 6: s = "\"(\" expected"; break;
                case 7: s = "\")\" expected"; break;
                case 8: s = "\"users\" expected"; break;
                case 9: s = "\"trips\" expected"; break;
                case 10: s = "\"like\" expected"; break;
                case 11: s = "\">\" expected"; break;
                case 12: s = "\"<\" expected"; break;
                case 13: s = "\"=\" expected"; break;
                case 14: s = "\">=\" expected"; break;
                case 15: s = "\"<=\" expected"; break;
                case 16: s = "\"or\" expected"; break;
                case 17: s = "\"and\" expected"; break;
                case 18: s = "??? expected"; break;
                case 19: s = "invalid Table"; break;
                case 20: s = "invalid Expr"; break;
                case 21: s = "invalid BOp"; break;
                case 22: s = "invalid Op"; break;
                case 23: s = "invalid Value"; break;

                default: s = "error " + n; break;
            }
            errorStream.WriteLine(errMsgFormat, line, col, s);
            count++;
        }

        public void SemErr(int line, int col, string s)
        {
            errorStream.WriteLine(errMsgFormat, line, col, s);
            count++;
        }

        public void SemErr(string s)
        {
            errorStream.WriteLine(s);
            count++;
        }

        public void Warning(int line, int col, string s)
        {
            errorStream.WriteLine(errMsgFormat, line, col, s);
        }

        public void Warning(string s)
        {
            errorStream.WriteLine(s);
        }
    } // Errors


    public class FatalError : Exception
    {
        public FatalError(string m) : base(m) { }
    }

}